package dvdstore.database;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import common.dvd.DVD;

public class DBAccessor {
	private RandomAccessFile databaseFile;
	
	private final int RECORD_LENGTH = 90;
	private final int MOVIE_ID_LENGTH = 10;
	private final int MOVIE_NAME_LENGTH = 40;
	private final int MOVIE_DIRECTOR_LENGTH = 40;
	
	private Map<Long, Long> recordPositions = new HashMap<Long, Long>();
	
	public DBAccessor(String fileName) {
		try {
			if(fileName != null) {
				databaseFile = new RandomAccessFile(new File(this.getClass().getClassLoader().getResource(fileName).getPath()), "rw");
				updateRecordsPositions();
			} else {
				throw new IllegalArgumentException("DBAccessor error: null filePath was passed.");
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	
	private void updateRecordsPositions() {
		try {
			byte[] recordBuffer = new byte[RECORD_LENGTH];
			byte[] movieIdBuffer = new byte[MOVIE_ID_LENGTH];
			long currPos;
			String movieIdString;
			
			databaseFile.seek(0);
			recordPositions.clear();
			
			while(databaseFile.getFilePointer() < databaseFile.length()) {
			
				currPos = databaseFile.getFilePointer();
				databaseFile.read(recordBuffer);
				
				movieIdBuffer = Arrays.copyOfRange(recordBuffer, 0, MOVIE_ID_LENGTH);
				movieIdString = new String(movieIdBuffer).trim();
				recordPositions.put(Long.valueOf(movieIdString), currPos);
			} 
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public Set<DVD> readAllRecords() {
		try {
			Set<DVD> allDvds= new HashSet<DVD>();
			
			byte[] recordBuffer = new byte[RECORD_LENGTH];
			byte[] movieIdBuffer = new byte[MOVIE_ID_LENGTH];
			byte[] movieNameBuffer = new byte[MOVIE_NAME_LENGTH];
			byte[] movieDirectorBuffer = new byte[MOVIE_DIRECTOR_LENGTH];
			String movieId, movieName, movieDirector;
			
			databaseFile.seek(0);
			
			while(databaseFile.getFilePointer() < databaseFile.length()) {
			
				databaseFile.read(recordBuffer);
				
				movieIdBuffer = Arrays.copyOfRange(recordBuffer, 0, MOVIE_ID_LENGTH);
				movieNameBuffer = Arrays.copyOfRange(recordBuffer, MOVIE_ID_LENGTH, MOVIE_ID_LENGTH + MOVIE_NAME_LENGTH);
				movieDirectorBuffer = Arrays.copyOfRange(recordBuffer, MOVIE_ID_LENGTH  + MOVIE_NAME_LENGTH, MOVIE_ID_LENGTH  + MOVIE_NAME_LENGTH + MOVIE_DIRECTOR_LENGTH);
				
				movieId = new String(movieIdBuffer).trim();
				movieName = new String(movieNameBuffer).trim();
				movieDirector = new String(movieDirectorBuffer).trim();
				
				DVD dvd = new DVD(Long.valueOf(movieId), movieName, movieDirector);
				allDvds.add(dvd);
			} 
			
			return allDvds;
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}
	
	public DVD readRecord(Long id) {
		try {
			byte[] recordBuffer = new byte[RECORD_LENGTH];
			byte[] movieIdBuffer = new byte[MOVIE_ID_LENGTH];
			byte[] movieNameBuffer = new byte[MOVIE_NAME_LENGTH];
			byte[] movieDirectorBuffer = new byte[MOVIE_DIRECTOR_LENGTH];
			String movieId, movieName, movieDirector;
			DVD dvd = null;
			
			databaseFile.seek(0);
			
			while(databaseFile.getFilePointer() < databaseFile.length()) {
			
				databaseFile.read(recordBuffer);
				
				movieIdBuffer = Arrays.copyOfRange(recordBuffer, 0, MOVIE_ID_LENGTH);
				movieId = new String(movieIdBuffer).trim();
				
				if(movieId.equals(String.valueOf(id))) {
					movieNameBuffer = Arrays.copyOfRange(recordBuffer, MOVIE_ID_LENGTH, MOVIE_ID_LENGTH + MOVIE_NAME_LENGTH);
					movieDirectorBuffer = Arrays.copyOfRange(recordBuffer, MOVIE_ID_LENGTH  + MOVIE_NAME_LENGTH, MOVIE_ID_LENGTH  + MOVIE_NAME_LENGTH + MOVIE_DIRECTOR_LENGTH);
					movieName = new String(movieNameBuffer).trim();
					movieDirector = new String(movieDirectorBuffer).trim();
					
					dvd = new DVD(Long.valueOf(movieId), movieName, movieDirector);
					break;
				}
			} 
			
			return dvd;
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}
	
	public Set<DVD> findRecordByTitle(String pattern) {
		try {
			Set<DVD> foundDvds= new HashSet<DVD>();
			
			byte[] recordBuffer = new byte[RECORD_LENGTH];
			byte[] movieIdBuffer = new byte[MOVIE_ID_LENGTH];
			byte[] movieNameBuffer = new byte[MOVIE_NAME_LENGTH];
			byte[] movieDirectorBuffer = new byte[MOVIE_DIRECTOR_LENGTH];
			String movieId, movieName, movieDirector;
			
			databaseFile.seek(0);
			
			while(databaseFile.getFilePointer() < databaseFile.length()) {
			
				databaseFile.read(recordBuffer);
				
				movieIdBuffer = Arrays.copyOfRange(recordBuffer, 0, MOVIE_ID_LENGTH);
				movieNameBuffer = Arrays.copyOfRange(recordBuffer, MOVIE_ID_LENGTH, MOVIE_ID_LENGTH + MOVIE_NAME_LENGTH);
				movieDirectorBuffer = Arrays.copyOfRange(recordBuffer, MOVIE_ID_LENGTH  + MOVIE_NAME_LENGTH, MOVIE_ID_LENGTH  + MOVIE_NAME_LENGTH + MOVIE_DIRECTOR_LENGTH);
				
				movieId = new String(movieIdBuffer).trim();
				movieName = new String(movieNameBuffer).trim();
				movieDirector = new String(movieDirectorBuffer).trim();
				
				if(movieName.matches(pattern)) {
					DVD dvd = new DVD(Long.valueOf(movieId), movieName, movieDirector);
					foundDvds.add(dvd);
				}
			} 
			
			return foundDvds;
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}
	
	public void updateRecord(DVD dvd) {
		Long pos = recordPositions.get(dvd.getId());
		if (pos == null) {
			System.out.println("Record update failed: record does not exist.");
		} else {
			deleteRecord(dvd);
			createRecord(dvd);
		}
	}
	
	public void createRecord(DVD dvd) {
		Long pos = recordPositions.get(dvd.getId());
		if (pos != null) {
			System.out.println("Record creation failed: record already exists.");
		} else {
			try {
				long recordPos;
				verifyRecord(dvd);
				StringBuilder sb = new StringBuilder();
				sb.append(fillWhitespaces(String.valueOf(dvd.getId()), MOVIE_ID_LENGTH));
				sb.append(fillWhitespaces(dvd.getTitle(), MOVIE_NAME_LENGTH));
				sb.append(fillWhitespaces(dvd.getDirector(), MOVIE_DIRECTOR_LENGTH));
				databaseFile.seek(databaseFile.length());
				recordPos = databaseFile.getFilePointer();
				databaseFile.writeBytes(sb.toString());
				recordPositions.put(dvd.getId(), recordPos);
			} catch (IOException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				System.out.println("Record creation failed: " + e.getMessage());
				e.printStackTrace();
			}
		}
	}
	
	public void deleteRecord(DVD dvd) {
		if(dvd != null) {
			Long recordPos = recordPositions.get(dvd.getId());
			if(recordPos != null) {
				int remainingBytesPos = (int) (recordPos + RECORD_LENGTH);
				byte[] remainingBytes;
				try {
					remainingBytes = new byte[((int) databaseFile.length()) - remainingBytesPos];
					databaseFile.seek(recordPos + RECORD_LENGTH);
					databaseFile.read(remainingBytes); 
					databaseFile.getChannel().truncate(recordPos);
					databaseFile.write(remainingBytes);
					updateRecordsPositions();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} 
				
			}
		} 
	}
	
	private void verifyRecord(DVD dvd) throws IllegalArgumentException{
		if(String.valueOf(dvd.getId()).length() > MOVIE_ID_LENGTH) {
			throw new IllegalArgumentException("DVD id length is larger than the maximum accepted");
		}
		if(dvd.getTitle().length() > MOVIE_NAME_LENGTH) {
			throw new IllegalArgumentException("DVD title length is larger than the maximum accepted");
		}
		if(dvd.getDirector().length() > MOVIE_DIRECTOR_LENGTH) {
			throw new IllegalArgumentException("DVD direcotr length is larger than the maximum accepted");
		}
	}
	
	private String fillWhitespaces(String str, int desiredLength) {
		StringBuilder sb = new StringBuilder(str);
		int difference = desiredLength - sb.length();
		for(int i = 0; i < difference; i++) {
			sb.append(" ");
		}
		return sb.toString();
	}
}
